package com.ruoyi.framework.interceptor;

import com.ruoyi.common.core.domain.entity.SysModule;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.service.ISysModuleService;
import com.ruoyi.common.utils.SecurityUtils;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.WithItem;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.context.ApplicationContext;

import java.util.Collection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.core.redis.RedisCache;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import com.ruoyi.common.enums.DataScopeType;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.springframework.data.redis.core.ValueOperations;

import static com.ruoyi.common.constant.CacheConstants.DEFAULT_MODULE_KEY;
import static com.ruoyi.common.constant.CacheConstants.DEPT_DAILY_ACCESS_PREFIX;

/**
 * MyBatis全局数据权限拦截器
 */
public class DataPermissionInterceptor implements InnerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(DataPermissionInterceptor.class);

    private ApplicationContext applicationContext;

    private RedisCache redisCache;

    private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.BASIC_ISO_DATE; // yyyyMMdd

    // 用于记录已注入权限的表
    private ThreadLocal<Set<String>> injectedTables = ThreadLocal.withInitial(HashSet::new);

    // 用于在递归过程中传递当前用户和模块信息
    private ThreadLocal<PermissionContext> permissionContext = ThreadLocal.withInitial(() -> null);

    public void setApplicationContext(ApplicationContext applicationContext, RedisCache redisCache) {
        this.applicationContext = applicationContext;
        this.redisCache = redisCache;
    }

    @Override
    @SuppressWarnings("rawtypes")
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        String originalSql = boundSql.getSql();
        try {
            handleSelectOperation(boundSql, originalSql);
        } catch (Throwable e) {
            logger.error("处理SELECT操作失败", e);
            throw new SQLException("处理SELECT操作失败", e);
        }
    }

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        SqlCommandType sqlCommandType = ms.getSqlCommandType();
        if (sqlCommandType == SqlCommandType.INSERT) {
            try {
                handleInsertOperation(boundSql);
            } catch (Throwable e) {
                logger.error("处理INSERT操作失败", e);
                throw new SQLException("处理INSERT操作失败", e);
            }
        }
    }

    /**
     * 处理SELECT操作
     */
    private void handleSelectOperation(BoundSql boundSql, String originalSql){
        try {
            // 1. 获取当前用户（如果未登录则放行）
            LoginUser loginUser = getLoginUserSafely();
            if (loginUser == null) {
                logger.debug("用户未登录，SQL放行：{}", originalSql);
                return;
            }
            
            SysUser currentUser = loginUser.getUser();
            if (currentUser == null || currentUser.isAdmin()) {
                logger.info("超级管理员不过滤，SQL已放行：{}", originalSql);
                return;
            }

            // 2. 解析SQL为AST
            Statement statement = CCJSqlParserUtil.parse(originalSql);
            if (!(statement instanceof Select)) {
                logger.warn("非SELECT语句，放行：{}", originalSql);
                return;
            }
            Select select = (Select) statement;

            // 3. 初始化上下文
            injectedTables.get().clear();
            permissionContext.set(new PermissionContext(currentUser));

            // 4. 递归处理SELECT语句
            processSelect(select);

            // 5. 将修改后的SQL写回BoundSql
            String newSql = select.toString();
            updateBoundSql(boundSql, newSql);

            // 6. 统计与日志
            try {
                incrementDeptDailyAccess(currentUser.getDeptId());
            } catch (Exception e) {
                logger.warn("记录部门日访问计数失败，deptId={}", currentUser.getDeptId(), e);
            }
            logger.info("SELECT操作 | 用户={} 部门ID={} | 原SQL长度={} 新SQL长度={}",
                    currentUser.getUserName(), currentUser.getDeptId(),
                    originalSql.length(), newSql.length());

        } catch (JSQLParserException e) {
            logger.error("解析SQL失败，放行：{}", originalSql, e);
        } finally {
            // 清理ThreadLocal，防止内存泄漏
            injectedTables.remove();
            permissionContext.remove();
        }
    }

    /**
     * 处理SELECT语句（入口）
     */
    private void processSelect(Select select) {
        // 处理WITH子句（CTE）
        List<WithItem> withItemsList = select.getWithItemsList();
        if (withItemsList != null && !withItemsList.isEmpty()) {
            for (WithItem withItem : withItemsList) {
                // 使用反射方式获取SelectBody
                try {
                    Method method = withItem.getClass().getMethod("getSelect");
                    Object selectObj = method.invoke(withItem);
                    if (selectObj instanceof Select) {
                        processSelectBodyDynamic(((Select) selectObj).getSelectBody());
                    }
                } catch (Exception e) {
                    logger.warn("处理WITH子句失败", e);
                }
            }
        }
        // 处理主查询体
        processSelectBodyDynamic(select.getSelectBody());
    }

    private void processSelectBodyDynamic(Object selectBody) {
        if (selectBody instanceof PlainSelect) {
            processPlainSelect((PlainSelect) selectBody);
        } else {
            String className = selectBody.getClass().getSimpleName();
            if ("SetOperationList".equals(className)) {
                try {
                    // 使用反射获取selects列表
                    Method getSelectsMethod = selectBody.getClass().getMethod("getSelects");
                    List<?> selects = (List<?>) getSelectsMethod.invoke(selectBody);
                    if (selects != null) {
                        for (Object subSelect : selects) {
                            processSelectBodyDynamic(subSelect);
                        }
                    }
                } catch (Exception e) {
                    logger.warn("处理SetOperationList失败", e);
                }
            }
        }
    }

    /**
     * 处理PlainSelect
     */
    private void processPlainSelect(PlainSelect plainSelect) {
        // 1. 处理FROM子句
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem != null) {
            processFromItem(plainSelect, fromItem);
        }

        // 2. 处理JOIN子句
        List<Join> joins = plainSelect.getJoins();
        if (joins != null && !joins.isEmpty()) {
            for (Join join : joins) {
                processJoin(plainSelect, join);
            }
        }
    }

    /**
     * 处理FROM子句中的FromItem
     */
    private void processFromItem(PlainSelect plainSelect, FromItem fromItem) {
        if (fromItem instanceof Table) {
            // 基本情况：遇到具体表，注入条件到WHERE
            Table table = (Table) fromItem;
            injectConditionToWhere(plainSelect, table);
        } else {
            // 处理子查询
            String className = fromItem.getClass().getSimpleName();
            if ("SubSelect".equals(className)) {
                try {
                    // 使用反射获取SelectBody
                    Method getSelectBodyMethod = fromItem.getClass().getMethod("getSelectBody");
                    Object selectBody = getSelectBodyMethod.invoke(fromItem);
                    processSelectBodyDynamic(selectBody);
                } catch (Exception e) {
                    logger.warn("处理FROM子查询失败", e);
                }
            }
        }
    }

    /**
     * 处理JOIN子句
     */
    private void processJoin(PlainSelect plainSelect, Join join) {
        FromItem rightItem = join.getRightItem();
        if (rightItem instanceof Table) {
            // 基本情况：遇到具体表
            Table table = (Table) rightItem;
            if (isImplicitJoin(join)) {
                // 隐式JOIN：条件注入到WHERE
                injectConditionToWhere(plainSelect, table);
            } else {
                // 显式JOIN：条件注入到ON
                injectConditionToJoinOn(join, table);
            }
        } else {
            // 处理子查询
            String className = rightItem.getClass().getSimpleName();
            if ("SubSelect".equals(className)) {
                try {
                    // 使用反射获取SelectBody
                    Method getSelectBodyMethod = rightItem.getClass().getMethod("getSelectBody");
                    Object selectBody = getSelectBodyMethod.invoke(rightItem);
                    processSelectBodyDynamic(selectBody);
                } catch (Exception e) {
                    logger.warn("处理JOIN子查询失败", e);
                }
            }
        }
    }

    /**
     * 判断是否为隐式JOIN
     */
    private boolean isImplicitJoin(Join join) {
        // 逗号连接或没有ON条件的JOIN视为隐式
        return join.isSimple() || join.getOnExpression() == null;
    }

    /**
     * 将权限条件注入到WHERE子句
     */
    private void injectConditionToWhere(PlainSelect plainSelect, Table table) {
        Expression condition = buildPermissionExpression(table);
        if (condition != null) {
            Expression where = plainSelect.getWhere();
            if (where == null) {
                plainSelect.setWhere(condition);
            } else {
                plainSelect.setWhere(new AndExpression(where, condition));
            }
        }
    }

    /**
     * 将权限条件注入到JOIN的ON子句
     */
    private void injectConditionToJoinOn(Join join, Table table) {
        Expression condition = buildPermissionExpression(table);
        if (condition != null) {
            try {
                Method getOnExpressionsMethod = join.getClass().getMethod("getOnExpressions");
                @SuppressWarnings("unchecked")
                Collection<Expression> onExpressions = (Collection<Expression>) getOnExpressionsMethod.invoke(join);
                
                if (onExpressions == null || onExpressions.isEmpty()) {
                    // 如果没有ON表达式，直接设置
                    java.lang.reflect.Method addOnExpressionMethod = join.getClass().getMethod("addOnExpression", Expression.class);
                    addOnExpressionMethod.invoke(join, condition);
                } else {
                    // 如果已有ON表达式，追加AND条件
                    Expression existingOn = onExpressions.iterator().next();
                    Expression newOn = new AndExpression(existingOn, condition);
                    
                    // 清空现有表达式并添加新的
                    onExpressions.clear();
                    java.lang.reflect.Method addOnExpressionMethod = join.getClass().getMethod("addOnExpression", Expression.class);
                    addOnExpressionMethod.invoke(join, newOn);
                }
            } catch (Exception e) {
                Expression on = join.getOnExpression();
                if (on == null) {
                    join.setOnExpression(condition);
                } else {
                    join.setOnExpression(new AndExpression(on, condition));
                }
            }
        }
    }

    /**
     * 构建权限过滤表达式
     */
    private Expression buildPermissionExpression(Table table) {
        String tableName = normalizeTableName(table);
        String alias = getTableAliasOrName(table);

        // 1. 检查是否已注入
        if (injectedTables.get().contains(alias)) {
            return null;
        }

        // 2. 检查表是否需要过滤
        if (!redisCache.isCacheSetMember(DEFAULT_MODULE_KEY, tableName)) {
            logger.debug("表{}未配置数据权限，跳过", tableName);
            return null;
        }

        // 3. 获取权限配置
        PermissionContext context = permissionContext.get();
        if (context == null) {
            return null;
        }

        SysUser currentUser = context.getCurrentUser();
        List<Long> roleIds = currentUser.getRoles().stream()
                .map(SysRole::getRoleId)
                .collect(Collectors.toList());

        ISysModuleService moduleService = applicationContext.getBean(ISysModuleService.class);
        List<SysModule> roleModules = moduleService.getModuleByTableNameAndRoleIds(tableName, roleIds);
        SysModule effectiveModule = null;
        if (roleModules != null && !roleModules.isEmpty()) {
            effectiveModule = roleModules.get(0);
        } else {
            effectiveModule = moduleService.getModuleByTableName(tableName);
        }

        if (effectiveModule == null) {
            logger.debug("表{}无有效模块配置，跳过", tableName);
            return null;
        }

        // 4. 生成过滤SQL片段
        String filterSql = buildFilterSql(effectiveModule, currentUser);
        if (filterSql == null || filterSql.trim().isEmpty()) {
            return null;
        }

        // 5. 将SQL片段转换为Expression（简化处理：直接解析为Expression）
        try {
            // 使用别名限定列名
            String conditionSql = filterSql.replace(effectiveModule.getTableName() + ".", alias + ".");
            Expression expression = CCJSqlParserUtil.parseCondExpression(conditionSql);
            
            // 标记已注入
            injectedTables.get().add(alias);
            logger.debug("为表{}(别名:{})注入权限条件：{}", tableName, alias, conditionSql);
            
            return expression;
        } catch (JSQLParserException e) {
            logger.error("解析权限条件失败：{}", filterSql, e);
            return null;
        }
    }

    /**
     * 规范化表名（去除schema、引号等）
     */
    private String normalizeTableName(Table table) {
        String name = table.getName();
        if (name != null) {
            // 去除反引号、双引号
            name = name.replace("`", "").replace("\"", "");
            // 去除schema前缀
            if (name.contains(".")) {
                name = name.substring(name.lastIndexOf(".") + 1);
            }
        }
        return name;
    }

    /**
     * 获取表的别名或表名
     */
    private String getTableAliasOrName(Table table) {
        Alias alias = table.getAlias();
        if (alias != null && alias.getName() != null) {
            return alias.getName();
        }
        return normalizeTableName(table);
    }

    /**
     * 更新BoundSql中的SQL
     */
    private void updateBoundSql(BoundSql boundSql, String newSql) {
        try {
            Field sqlField = BoundSql.class.getDeclaredField("sql");
            sqlField.setAccessible(true);
            sqlField.set(boundSql, newSql);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            logger.error("无法修改BoundSql的SQL", e);
            throw new RuntimeException("未能将过滤器 SQL 注入到绑定 SQL 中", e);
        }
    }

    /**
     * 权限上下文（用于在递归过程中传递信息）
     */
    private static class PermissionContext {
        private final SysUser currentUser;

        public PermissionContext(SysUser currentUser) {
            this.currentUser = currentUser;
        }

        public SysUser getCurrentUser() {
            return currentUser;
        }
    }

    /**
     * 处理INSERT操作
     */
    private void handleInsertOperation(BoundSql boundSql) {
        String originalSql = boundSql.getSql();
        
        // 1. 获取当前用户（如果未登录则放行）
        LoginUser loginUser = getLoginUserSafely();
        if (loginUser == null) {
            logger.debug("用户未登录，INSERT操作放行：{}", originalSql);
            return;
        }
        
        SysUser currentUser = loginUser.getUser();
        if (currentUser == null) {
            logger.debug("用户信息为空，INSERT操作放行：{}", originalSql);
            return;
        }

        // 2. 解析表名
        String tableName = parseTableNameFromUpdateSql(originalSql);
        if (tableName == null){
            logger.info("数据权限拦截器未能解析表名，SQL已放行：{}", originalSql);
            return;
        } else if (!redisCache.isCacheSetMember(DEFAULT_MODULE_KEY, tableName)) {
            logger.info("表{}未配置默认数据访问范围，SQL放行：{}", tableName, originalSql);
            return;
        }

        // 3. 超管权限验证
        if (currentUser.isAdmin()) {
            if (currentUser.getDeptId() == null) {
                throw new ServiceException("无所属部门的超级管理员无法进行Insert操作");
            }
            // 有部门ID的超管直接放行
            logger.info("超级管理员(部门ID={})执行INSERT操作，直接放行", currentUser.getDeptId());
            return;
        }

        // 4. 普通用户必须有部门ID
        if (currentUser.getDeptId() == null) {
            throw new ServiceException("普通用户必须指定部门ID才能执行INSERT操作");
        }

        // 5. 自动注入部门ID到INSERT语句
        String newSql = injectDeptIdToInsertSql(originalSql, currentUser.getDeptId());
        if (!newSql.equals(originalSql)) {
            // 更新BoundSql中的SQL
            try {
                Field sqlField = BoundSql.class.getDeclaredField("sql");
                sqlField.setAccessible(true);
                sqlField.set(boundSql, newSql);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                logger.error("无法直接修改BoundSql的SQL，尝试通过MetaObject", e);
            }
        }

        // 6. 记录日志
        logger.info("INSERT操作 | 用户={} 部门ID={} 表={} 部门ID注入={}",
                currentUser.getUserName(),
                currentUser.getDeptId(),
                tableName,
                newSql.equals(originalSql) ? "失败" : "成功"
        );
    }

    private LoginUser getLoginUserSafely() {
        try {
            return SecurityUtils.getLoginUser();
        } catch (ServiceException e) {
            logger.info("未找到用户信息: {}", e.getMessage());
            return null;
        }
    }

    // 解析Update SQL语句的表名
    public String parseTableNameFromUpdateSql(String sql) {
        try {
            System.out.println(sql);
            Statement statement = CCJSqlParserUtil.parse(sql);
            if (statement instanceof Insert) {
                Insert insert = (Insert) statement;
                Table table = insert.getTable();
                return table.getName();
            } else if (statement instanceof Update) {
                Update update = (Update) statement;
                Table table = update.getTable();
                return table.getName();
            }
        } catch (JSQLParserException | ClassCastException e) {
            logger.error("解析SQL表名失败", e);
        }
        return null;
    }

    // 生成过滤条件
    public String buildFilterSql(SysModule module, SysUser user) {
        // dataScope: 1-全部 2-本部门及下级 3-本部门 4-仅本人 5-自定义
        Integer dataScope = module.getDataScope();
        String tableAlias = module.getTableName();
        if (dataScope == null) return null;
        DataScopeType scopeType = DataScopeType.fromCode(dataScope);
        if (scopeType == null) {
            return null;
        }
        return scopeType.buildFilterSql(tableAlias, user, module.getScopeExpr());
    }

    // 注入过滤条件到SELECT SQL
    public String injectFilterToSql(String sql, String filterSql) {
        try {
            Statement statement = CCJSqlParserUtil.parse(sql);
            if (statement instanceof Select) {
                Select select = (Select) statement;
                PlainSelect plainSelect = select.getPlainSelect();
                Expression where = plainSelect.getWhere();
                Expression filter = CCJSqlParserUtil.parseCondExpression(filterSql);
                if (where != null) {
                    plainSelect.setWhere(new AndExpression(where, filter));
                } else {
                    plainSelect.setWhere(filter);
                }
                return select.toString();
            }
        } catch (JSQLParserException e) {
            logger.error("数据权限注入失败", e);
        }
        return sql;
    }

    // 自动注入部门ID到INSERT SQL
    public String injectDeptIdToInsertSql(String sql, Long deptId) {
        // 检查是否已经包含dept_id列
        if (sql.toLowerCase().contains("dept_id")) {
            logger.info("INSERT语句已包含dept_id列，无需注入");
            return sql;
        }

        try {
            int columnsStart = sql.indexOf("(") + 1;
            int columnsEnd = sql.indexOf(")");
            int valuesStart = sql.indexOf("VALUES") + 6;
            int valuesEnd = sql.lastIndexOf(")");

            if (columnsStart > 0 && columnsEnd > columnsStart && valuesStart > 6 && valuesEnd > valuesStart) {
                // 在列名后添加dept_id
                String columns = sql.substring(columnsStart, columnsEnd);
                String newColumns = columns + ", dept_id";
                
                // 在VALUES后添加dept_id的值
                String values = sql.substring(valuesStart, valuesEnd);
                String newValues = values + ", " + deptId;
                
                // 构建新的SQL
                String newSql = sql.substring(0, columnsStart) + newColumns + 
                               sql.substring(columnsEnd, valuesStart) + newValues + 
                               sql.substring(valuesEnd);
                
                logger.info("成功注入部门ID {} 到INSERT语句", deptId);
                return newSql;
            }
        } catch (Exception e) {
            logger.warn("注入部门ID失败，使用原始SQL: {}", e.getMessage());
        }
        
        return sql;
    }

    // 使用Redis记录"部门-日期(天)"访问次数
    private void incrementDeptDailyAccess(Long deptId) {
        if (deptId == null || applicationContext == null) {
            return;
        }
        // 确保redisCache已被注入
        if (redisCache == null) {
            redisCache = applicationContext.getBean(RedisCache.class);
        }
        String day = LocalDate.now().format(DAY_FMT);
        String key = DEPT_DAILY_ACCESS_PREFIX + deptId + ":" + day;
        // 自增计数
        ValueOperations<String, Long> valueOperations = (ValueOperations<String, Long>) redisCache.redisTemplate.opsForValue();
        Long newVal = valueOperations.increment(key);
        // 如果是首次创建（值为1），设置过期时间（60天），避免key长期累积
        if (newVal != null && newVal == 1L) {
            redisCache.expire(key, 60L, TimeUnit.DAYS);
        }
    }
} 